#include "board.h"
#include <string.h>

#include "xparameters.h"
#include "xtmrctr.h"
#include "xintc.h"
#include "xgpio.h"

#define TMRCTR_DEVICE_ID      XPAR_TMRCTR_0_DEVICE_ID
#define TMRCTR_BASEADDR       XPAR_TMRCTR_0_BASEADDR
#define INTC_DEVICE_ID        XPAR_INTC_0_DEVICE_ID
#define TMRCTR_INTERRUPT_ID   XPAR_MICROBLAZE_0_AXI_INTC_AXI_TIMER_0_INTERRUPT_INTR
#define TIMER_CNTR_0          0
#define GPIO_OUTPUT_DEVICE_ID	XPAR_GPIO_0_DEVICE_ID
#define LED_CHANNEL           1

// reset after 1 sec
#define RESET_VALUE	          (0xFFFFFFFF - XPAR_AXI_TIMER_0_CLOCK_FREQ_HZ)

XIntc InterruptController;
XTmrCtr xil_timer;
XGpio GpioOutput;

void TimerCounterHandler(void *CallBackRef, u8 TmrCtrNumber);

static uint32_t gpio_flag = 0xFFFF;
std::uint64_t gpt_system_tick = 0;
std::uint32_t gpio_addr;

void Board::toggle_led() {
  * ((volatile u32 *) gpio_addr) = gpio_flag;
  gpio_flag ^= 0xFFFF;
}

void Board::init_start() {
  XGpio_Initialize(&GpioOutput, GPIO_OUTPUT_DEVICE_ID);
  XGpio_SetDataDirection(&GpioOutput, LED_CHANNEL, 0x0);

  XTmrCtr_Initialize(&xil_timer, TMRCTR_DEVICE_ID);
  XTmrCtr_SelfTest(&xil_timer, TIMER_CNTR_0);

  XIntc_Initialize(&InterruptController, INTC_DEVICE_ID);
  XIntc_Connect(&InterruptController,
                TMRCTR_INTERRUPT_ID,
                (XInterruptHandler)XTmrCtr_InterruptHandler,
                (void*) &xil_timer);

  Xil_ExceptionInit();
  Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
                               (Xil_ExceptionHandler) XIntc_InterruptHandler,
                               &InterruptController);

  XTmrCtr_SetHandler(&xil_timer,
                     TimerCounterHandler,
                     &xil_timer);
  XTmrCtr_SetOptions(&xil_timer,
                      TIMER_CNTR_0,
                      XTC_INT_MODE_OPTION | XTC_AUTO_RELOAD_OPTION);
  XTmrCtr_SetResetValue(&xil_timer, TIMER_CNTR_0, RESET_VALUE);

  gpio_addr = GpioOutput.BaseAddress + XGPIO_DATA_OFFSET;
}

void Board::enable_interrupts() {
	microblaze_enable_interrupts();

  XIntc_Start(&InterruptController, XIN_REAL_MODE);
  XIntc_Enable(&InterruptController, TMRCTR_INTERRUPT_ID);
  Xil_ExceptionEnable();
}

void Board::disable_interrupts() {
	microblaze_disable_interrupts();
}

void Board::enable_timer() {
  XTmrCtr_Start(&xil_timer, TIMER_CNTR_0);
}

void Board::disable_timer() {
  XTmrCtr_Stop(&xil_timer, TIMER_CNTR_0);
  XIntc_Disable(&InterruptController, TMRCTR_INTERRUPT_ID);
}

rl::TimePoint Board::get_now() {
  return gpt_system_tick;
}

void adjust_timer(const rotor_light::Duration &value) {
  gpt_system_tick += value;
}

void Board::delay() {}

// on interrupt add 1 second
void TimerCounterHandler(void *CallBackRef, u8 TmrCtrNumber)
{
  gpt_system_tick ++;
}
